Closed pkitatta closed 4 years ago
Hi) do you have visualization of what you want to achieve? Just wants to make sure that I understood your code sample in a right way
Yes I do
This one I actually did with sticky_header by slighfoot but as I read your comment in the issues there, the header is wobbly - not very cool. And also I need to implement scroll to position so I chose to look for another plugin. flutter_sticky_header the plugin I use for the above code example has its issues - the header is at the bottom of the list.
Widget buildListMessage() {
print('conversationId in the list wig is: $hasConversationId');
print('private chat it: $privateChatId');
return Flexible(
child: privateChatId == ''
? Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Color(0xFF180018))))
: StreamBuilder(
///STREAM CONVARSATION WITH THIS USER
stream: _messages,
builder: (context, snapshot) {
if (!snapshot.hasData) {
//If there is no conversation of this is set hasConversation
hasConversationId = false;
return Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(
Color(0xFF180018))));
} else {
listMessage = snapshot.data.documents;
if (snapshot.data.documents.length > 0)
//If there is no conversation of this is set hasConversation
hasConversationId = true;
print(
'conversationId inside list return: $hasConversationId');
var newMap = groupBy(
listMessage,
(obj) => DateFormat('y-MM-dd').format(
DateTime.fromMillisecondsSinceEpoch(
int.parse(obj['timestamp']))));
// print(newMap);
// for (var obj in newMap.entries) {
// print(obj.key);
// for (var value in obj.value) print(value.toString());
// }
return ListView(
padding: EdgeInsets.only(top: 0),
children: newMap.entries
.map<Widget>((item) => buildDateItem(context, item))
.toList(),
reverse: true,
controller: listScrollController,
);
},
),
);
}
Widget buildDateItem(BuildContext context, item) {
print('Map in listview: ${item.value}');
return StickyHeader(
header: Center(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 5.0),
margin: EdgeInsets.only(top: 5.0, bottom: 5.0),
decoration: BoxDecoration(
color: Colors.black54,
borderRadius: BorderRadius.all(Radius.circular(30))),
child: Text(
dateFx(item.key),
style: const TextStyle(
color: Colors.white,
),
textAlign: TextAlign.center,
),
),
),
content: Column(
children: item.value.reversed
.map<Widget>((item) => buildItem(context, item))
.toList(),
),
);
}
This is the implementation for sticky_header
as I understood - only days should be sticked to the bottom? First thought was that you also need client photo to be sticked too with dates)
I will provide some code samples for your case in a few hours if it's ok?
as I understood - only days should be sticked to the bottom? First thought was that you also need client photo to be sticked too with dates)
No date sticks at the top. In the photo I didn't scroll to the bottom. I know the design is confusing but it's because am using a Stack widget for the body with AppBar positioned at the top. Here is a better photo.
I will provide some code samples for your case in a few hours if it's ok?
That very OK thanks.
ok, so here is a very raw example
Widget _buildChat(data) {
return InfiniteList(
anchor: 1,
direction: InfiniteListDirection.multi,
minChildCount: data.length * -1, /// your total days count with minus,
maxChildCount: 0,
builder: (context, index) {
/// consider that index will be negative only, starting from -1
final messageIndex = (index + 1) * -1; /// if index == -1 - result will be 0, -2 -> 1 and etc.
/// get needed data from `data` by `messageIndex`
return InfiniteListItem(
headerAlignment: HeaderAlignment.topCenter,
headerBuilder: (context) => /// you header,
contentBuilder: (context) => Container(
margin: const EdgeInsets.top( /// put your header height here ),
child: Column(
children: [
/// your messages goes here
],
),
),
minOffsetProvider: (state) => /// your header height,
);
},
);
}
But take into account that for your particular case probably none of the sticky header packages won't help you to solve the probable performance issue (among packages that I'm aware at least). Day block will be rendered on-demand, but the case here that you quite likely will have tons and tons of messages for one particular day, which means that all packages will require to render all of them, that regular ListView
is trying to avoid by rendering each scroll item on demand
btw, header size is needed to define offset for content (same as padding around content). For now headers always overlay content (#19)
like an option to solve day item data (with huge amount of data), use headerStateBuilder
. It will be invoked each time when sticky header changes it's position (https://github.com/TatsuUkraine/flutter_sticky_infinite_list#state). So when it's about to get to the end edge - you can rebuild content with adding there more item for render. But again, it's more like a workaround, I would probably build own solution for your particular case)
@TatsuUkraine thanks a for the honesty, insight, and example code. The sample code might do another person good as well so I think it would not hurt to put it in your documentation.
You are probably right about the performance, I might need to build my own and use this plugin in another part of the app. Thanks for the help.
What problem do you have with SliverStickyHeader? Also I curious if it renders each perticular message on demand or whole day block at once?
@pkitatta if it renders each message on demand, and problem just in header positioning, I think I know one workaround how you can fix it
The sample code might do another person good as well so I think it would not hurt to put it in your documentation.
I have not exact same, but similar code example here https://github.com/TatsuUkraine/flutter_sticky_infinite_list/blob/master/README.md#reverse-infinite-scroll
@pkitatta if it renders each message on demand, and problem just in header positioning, I think I know one workaround how you can fix it
@TatsuUkraine SliverStickyHeader is great. With its lack of support for reversed list as its main problem. It renders on demand as it uses SliverList and SliverChildBuilderDelegate for the list content. If you can do something about the header at the bottom then please please please you are most welcome because will save me some time to try and build my own animations for the header.
@pkitatta I honestly tried the workaround, that I was thinking could solve an issue, but it still pinned header to the bottom(
and now I recall why I decided to build my own package, I had similar problems in addition to the fact that it can't be used within multi-directional infinite list)
I looked at the source code of the sliver sticky header package and found that it always refers to the scroll starting position. So like an option, you can fork it and just change the way how it calculates position for the header, which I believe happens here https://github.com/letsar/flutter_sticky_header/blob/master/lib/src/rendering/sliver_sticky_header.dart#L216
Yeah, am not yet advanced to fork and change things. I don't even know where to start. I have used flutter for three months but while migrating my project from ionic with a deadline. So it's been a crash course for me.
For now and doing things the simple way. This is my starting point: https://flutter.dev/docs/cookbook/lists/mixed-list
I have change my code as you can see
Widget buildListMessage() {
print('conversationId in the list wig is: $hasConversationId');
print('private chat it: $privateChatId');
return Flexible(
child: privateChatId == ''
? Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Color(0xFF180018))))
: StreamBuilder(
///STREAM CONVARSATION WITH THIS USER
stream: _messages,
builder: (context, snapshot) {
if (!snapshot.hasData) {
//If there is no conversation of this is set hasConversation
hasConversationId = false;
return Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(
Color(0xFF180018))));
} else {
// listMessage = snapshot.data.documents;
if (snapshot.data.documents.length > 0)
//If there is no conversation of this is set hasConversation
hasConversationId = true;
print(
'conversationId inside list return: $hasConversationId');
//Group the messages from the database by date
var newMap = groupBy(
snapshot.data.documents,
(obj) => obj['date']);
print(newMap);
listMessage = [];
//Make the grouped data into one flat list
for (var obj in newMap.entries){
//Load the list with a group of messages
for(var value in obj.value){
listMessage.add(value);
}
//Add date header after adding child messages above
listMessage.add({'messageHeading': obj.key});
}
return ListView.builder(
padding: EdgeInsets.all(10.0),
itemBuilder: (context, index) =>
buildDateItem(context, listMessage[index]),
itemCount: snapshot.data.documents.length,
reverse: true,
controller: listScrollController,
);
}
},
),
);
}
Widget buildDateItem(BuildContext context, item) {
print('date: ${item}');
if(item['messageHeading'] != null){
print('date: ${item['messageHeading']}');
return Center(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 5.0),
margin: EdgeInsets.only(top: 5.0, bottom: 5.0),
decoration: BoxDecoration(
color: Colors.black54,
borderRadius: BorderRadius.all(Radius.circular(30))),
child: Text(
dateFx(item['messageHeading'].toString()),
style: const TextStyle(
color: Colors.white,
),
textAlign: TextAlign.center,
),
),
);
}else{
return InkWell(
onTap: longPressed
? () {
print('in onTap fx');
//Check if already selected if true remove and if last item reset longPress param
if (_selectedMessages.contains(item)) {
print('in onTap fx, if statement');
setState(() {
_selectedMessages.remove(item);
});
print('selectedList lenght ${_selectedMessages.length}');
if (_selectedMessages.length == 0) {
setState(() {
longPressed = false;
isSelected = false;
});
}
} else {
setState(() {
_selectedMessages.add(item);
});
print('in onTap fx, else statement');
print('selectedList lenght ${_selectedMessages.length}');
}
}
: () {
print('mere tap');
},
onLongPress: !longPressed
? () {
print('in onLongTap fx');
Feedback.forLongPress(context);
setState(() {
longPressed = true;
isSelected = true;
_selectedMessages.add(item);
});
print('selectedList lenght ${_selectedMessages.length}');
}
: null,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
color: _selectedMessages.contains(item) ? Colors.white24 : null,
child: item['type'] == 0
? bubble(
item,
TextSpan(text: item['content']),
DateFormat('kk:mm').format(
DateTime.fromMillisecondsSinceEpoch(
int.parse(item['timestamp']))),
true,
item['idFrom'] == id ? true : false,
)
: item['type'] == 1
? replyBubble(
item,
TextSpan(text: item['content']),
DateFormat('kk:mm').format(
DateTime.fromMillisecondsSinceEpoch(
int.parse(item['timestamp']))),
true,
item['idFrom'] == id ? true : false,
)
: item['type'] == 3
? billBubble(
item,
TextSpan(text: item['content']),
DateFormat('kk:mm').format(
DateTime.fromMillisecondsSinceEpoch(
int.parse(item['timestamp']))),
true,
item['idFrom'] == id ? true : false,
)
: Container(),
)
],
),
);
}
}
Instead for looping through a grouped list of lists I create one flat list and then use the flutter example.
Now all I need is to get a container to animate as a sticky header - this is where my challenge is going to be.
Ok. As I said before, you PROBABLY will have performance issue if you have tons and tons of messages per day. So you can try to use code sample that I put before and measure fps in dev and prod builds with big amount of data. Flutter is quite fast with rendering, so quite possibly that you won't have any problems, but I would double check) if so, you can simply use my package and if you get any performance problems, you always can get back to your latest variant)
Also I would recommend you to split your code into smaller widgets, in that way flutter will try to optimize any rebuild process to avoid building widgets that wasn't changed during rebuild https://flutter.dev/docs/perf/rendering/best-practices#controlling-build-cost
@pkitatta I honestly tried the workaround, that I was thinking could solve an issue, but it still pinned header to the bottom(
and now I recall why I decided to build my own package, I had similar problems in addition to the fact that it can't be used within multi-directional infinite list)
I looked at the source code of the sliver sticky header package and found that it always refers to the scroll starting position. So like an option, you can fork it and just change the way how it calculates position for the header, which I believe happens here https://github.com/letsar/flutter_sticky_header/blob/master/lib/src/rendering/sliver_sticky_header.dart#L216
@TatsuUkraine I finally got the solution. I reversed the list at the point I was grouping the data
var newMap = groupBy(
listMessage.reversed, <------------------------------ at this point
(obj) => DateFormat('y-MM-dd').format(
DateTime.fromMillisecondsSinceEpoch(
int.parse(obj['timestamp']))));
Now the header is no longer at the bottom.
@pkitatta I can assume you using forward direction scroll list also?
@pkitatta I can assume you using forward direction scroll list also?
Everything is OK. I can scroll up and down the list. I have just run into one problem through: Because the headers and its messages are grouped in slivers, each sliver has its own message indexing that starts from 0. This is something I didn't face with sticky header and am assuming your plugin doesn't have this problem. Unified indexing for all the children or messages in the list is needed so that I can use it to say, when I click on the reply message the list should be a to scroll to the position to the original message.
@pkitatta I can assume you using forward direction scroll list also?
Everything is OK. I can scroll up and down the list. I have just run into one problem through: Because the headers and its messages are grouped in slivers, each sliver has its own message indexing that starts from 0. This is something I didn't face with sticky header and am assuming your plugin doesn't have this problem. Unified indexing for all the children or messages in the list is needed so that I can use it to say, when I click on the reply message the list should be a to scroll to the position to the original message.
I was asking about scroll type, because if you using NOT reversed scroll you can run into an issue with it's position during dynamic data fetch, because starting point of such scroll is placed at the top rather than at the bottom. Any new messages, that will be placed at the end of your list, won't scroll your content to the bottom edge. Any old massages placed at the top during scroll up and lazy data load will shift your content on new messages size
I get what you are saying.
if any help is needed, feel free to open new issue
Hi, thank you for this plugin, it looks nice even though and struggling to quickly integrate it in my code.
Would it be possible to please give a real list example as could be used in an app?
Forexample below is my code using another sticky header plugin, how can I quickly implement your plugin in this flow.
Thanks.