Skip to content

Commit 013c739

Browse files
committed
msglist: MessageList now has a scroll to bottom button
1 parent 71ec0cc commit 013c739

File tree

1 file changed

+49
-2
lines changed

1 file changed

+49
-2
lines changed

lib/widgets/message_list.dart

+49-2
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ class _MessageListPageState extends State<MessageListPage> {
5858

5959
child: Expanded(
6060
child: MessageList(narrow: widget.narrow))),
61-
6261
ComposeBox(controllerKey: _composeBoxKey, narrow: widget.narrow),
6362
]))));
6463
}
@@ -97,6 +96,31 @@ class MessageListAppBarTitle extends StatelessWidget {
9796
}
9897
}
9998

99+
class ScrollToBottomButton extends StatelessWidget {
100+
const ScrollToBottomButton({super.key, required this.scrollController, required this.visibleValue});
101+
final ValueNotifier<bool> visibleValue;
102+
final ScrollController scrollController;
103+
104+
Future<void> _navigateToBottom(BuildContext context) async {
105+
scrollController.animateTo(0, duration: const Duration(milliseconds: 300), curve: Curves.easeIn);
106+
}
107+
108+
@override
109+
Widget build(BuildContext context) {
110+
return ValueListenableBuilder<bool>(
111+
builder: (BuildContext context, bool value, Widget? child) {
112+
return (value && child != null) ? child : const SizedBox.shrink();
113+
},
114+
valueListenable: visibleValue,
115+
// TODO: fix hardcoded values for size and style here
116+
child: IconButton(
117+
tooltip: "Scroll to bottom",
118+
icon: const Icon(Icons.expand_circle_down_rounded),
119+
iconSize: 40,
120+
style: IconButton.styleFrom(foregroundColor: const HSLColor.fromAHSL(0.5,240,0.96,0.68).toColor()),
121+
onPressed: () => _navigateToBottom(context)));
122+
}
123+
}
100124

101125
class MessageList extends StatefulWidget {
102126
const MessageList({super.key, required this.narrow});
@@ -109,6 +133,9 @@ class MessageList extends StatefulWidget {
109133

110134
class _MessageListState extends State<MessageList> {
111135
MessageListView? model;
136+
final ScrollController scrollController = ScrollController();
137+
138+
final ValueNotifier<bool> _scrollToBottomVisibleValue = ValueNotifier<bool>(false);
112139

113140
@override
114141
void didChangeDependencies() {
@@ -161,7 +188,26 @@ class _MessageListState extends State<MessageList> {
161188
child: Center(
162189
child: ConstrainedBox(
163190
constraints: const BoxConstraints(maxWidth: 760),
164-
child: _buildListView(context))))));
191+
child: NotificationListener<ScrollEndNotification>(
192+
onNotification: (scrollEnd) {
193+
final metrics = scrollEnd.metrics;
194+
if (metrics.atEdge && metrics.pixels == 0) {
195+
_scrollToBottomVisibleValue.value = false;
196+
} else {
197+
_scrollToBottomVisibleValue.value = true;
198+
}
199+
return true;
200+
},
201+
child: Stack(
202+
children: <Widget>[
203+
_buildListView(context),
204+
Container(
205+
alignment: Alignment.bottomRight,
206+
child: ScrollToBottomButton(scrollController: scrollController, visibleValue: _scrollToBottomVisibleValue),
207+
),
208+
]
209+
),
210+
))))));
165211
}
166212

167213
Widget _buildListView(context) {
@@ -179,6 +225,7 @@ class _MessageListState extends State<MessageList> {
179225
_ => ScrollViewKeyboardDismissBehavior.manual,
180226
},
181227

228+
controller: scrollController,
182229
itemCount: length,
183230
// Setting reverse: true means the scroll starts at the bottom.
184231
// Flipping the indexes (in itemBuilder) means the start/bottom

0 commit comments

Comments
 (0)