@@ -7,35 +7,111 @@ import 'package:provider/provider.dart';
77import 'package:qiscus_chat_sample/src/state/state.dart' ;
88import 'package:qiscus_chat_sample/src/widget/app_bar.dart' ;
99import 'package:qiscus_chat_sample/src/widget/chat_bubble.dart' ;
10+ import 'package:rxdart/rxdart.dart' ;
1011
1112class ChatPage extends StatefulWidget {
12- ChatPage ({this .roomId});
13-
1413 final int roomId;
1514
15+ ChatPage ({this .roomId});
16+
1617 @override
1718 State <StatefulWidget > createState () => _ChatState ();
1819}
1920
2021class _ChatState extends State <ChatPage > {
2122 final scrollController = ScrollController ();
23+ final scaffoldKey = GlobalKey <ScaffoldState >();
24+ final _debounceTimer = TimerStream (true , const Duration (milliseconds: 500 ));
25+ final scroll$ = StreamController <ScrollNotification >();
26+ final typing$ = StreamController ();
2227
23- @override
24- void initState () {
25- super .initState ();
26- scheduleMicrotask (() {
27- _getRoom ();
28- _getMessages ();
29- });
30- }
28+ bool isLoading = false ;
29+ MessageState _messageState;
30+ RoomState _roomState;
3131
3232 @override
3333 Widget build (BuildContext ctx) {
3434 return Consumer <RoomState >(
3535 builder: (_, state, __) => Scaffold (
36- appBar: appBar (
37- room: state.currentRoom,
38- onBack: () => Navigator .pushReplacementNamed (context, '/login' ),
36+ key: scaffoldKey,
37+ appBar: AppBar (
38+ leading: FlatButton (
39+ onPressed: () => Navigator .pop (context),
40+ child: Icon (
41+ Icons .chevron_left,
42+ color: Colors .white,
43+ size: 34 ,
44+ ),
45+ ),
46+ centerTitle: false ,
47+ title: Container (
48+ child: Row (
49+ crossAxisAlignment: CrossAxisAlignment .start,
50+ mainAxisAlignment: MainAxisAlignment .start,
51+ children: < Widget > [
52+ CircleAvatar (
53+ backgroundImage: (state.currentRoom != null )
54+ ? Image .network (
55+ state.currentRoom.avatarUrl,
56+ fit: BoxFit .fill,
57+ height: 34 ,
58+ width: 34 ,
59+ ).image
60+ : Image .asset (
61+ 'assets/ic-default-room-avatar.png' ,
62+ fit: BoxFit .fill,
63+ height: 34 ,
64+ width: 34 ,
65+ ).image,
66+ ),
67+ Padding (
68+ padding: const EdgeInsets .only (left: 20.0 ),
69+ child: Column (
70+ crossAxisAlignment: CrossAxisAlignment .start,
71+ children: < Widget > [
72+ if (state.currentRoom != null )
73+ Text (state.currentRoom.name,
74+ style: TextStyle (fontSize: 18 )),
75+ if (state.currentRoom == null ) Text ('Loading...' ),
76+ if (state.currentRoom != null )
77+ Consumer <RoomState >(
78+ builder: (_, state, __) => MultiProvider (
79+ providers: [
80+ StreamProvider .value (value: state.onTyping),
81+ StreamProvider .value (value: state.onPresence),
82+ ],
83+ child: AppBarStatus (room: state.currentRoom),
84+ ),
85+ ),
86+ ],
87+ ),
88+ ),
89+ ],
90+ ),
91+ ),
92+ actions: < Widget > [
93+ PopupMenuButton (
94+ itemBuilder: (ctx) => < PopupMenuEntry > [
95+ PopupMenuItem (
96+ child: GestureDetector (
97+ onTap: () {
98+ Scaffold .of (ctx).showSnackBar (SnackBar (
99+ duration: const Duration (milliseconds: 500 ),
100+ content:
101+ Text ('Show room ${state .currentRoomId } detail' ),
102+ action: SnackBarAction (
103+ label: 'Close' ,
104+ onPressed: () =>
105+ Scaffold .of (ctx).hideCurrentSnackBar (),
106+ ),
107+ ));
108+ },
109+ child: Text ('Detail' ),
110+ ),
111+ )
112+ ],
113+ )
114+ ],
39115 ),
40116 body: Column (
41117 children: [
@@ -48,12 +124,41 @@ class _ChatState extends State<ChatPage> {
48124 );
49125 }
50126
51- Widget buildLoading () {
52- return Expanded (
53- child: Center (
54- child: CircularProgressIndicator (),
55- ),
56- );
127+ @override
128+ void deactivate () {
129+ _roomState.unsubscribe (_roomState.currentRoom);
130+ super .deactivate ();
131+ }
132+
133+ @override
134+ void dispose () async {
135+ super .dispose ();
136+ scrollController.dispose ();
137+ await scroll$.close ();
138+ await typing$.close ();
139+ }
140+
141+ @override
142+ void initState () {
143+ super .initState ();
144+ scheduleMicrotask (() {
145+ _messageState = Provider .of <MessageState >(context, listen: false );
146+ _roomState = Provider .of <RoomState >(context, listen: false );
147+ });
148+
149+ scheduleMicrotask (() {
150+ _getRoom ();
151+ _getMessages ();
152+
153+ scroll$.stream
154+ .debounce ((_) => _debounceTimer)
155+ .where ((n) => n.metrics.pixels == n.metrics.maxScrollExtent)
156+ .listen ((_) => _loadMore ());
157+
158+ typing$.stream
159+ .debounce ((_) => _debounceTimer)
160+ .listen ((_) => _roomState.publishTyping (widget.roomId));
161+ });
57162 }
58163
59164 Widget _form (BuildContext ctx) {
@@ -62,13 +167,16 @@ class _ChatState extends State<ChatPage> {
62167 final key = GlobalKey <FormState >();
63168 var messageState = Provider .of <MessageState >(context, listen: false );
64169
170+ controller.addListener (() {
171+ print ('controller listener' );
172+ });
173+
65174 final submit = (String message) {
66175 messageState.submit (
67176 roomId: roomId,
68177 message: message,
69178 );
70179 controller.text = '' ;
71- _animateScrollToBottom ();
72180 };
73181 return Form (
74182 key: key,
@@ -77,11 +185,9 @@ class _ChatState extends State<ChatPage> {
77185 IconButton (
78186 icon: Icon (Icons .attach_file),
79187 onPressed: () async {
80- print ('get file' );
81188 var file = await FilePicker .getFile ();
82189 if (file != null ) {
83190 await messageState.sendFile (file);
84- _animateScrollToBottom ();
85191 }
86192 },
87193 ),
@@ -114,45 +220,12 @@ class _ChatState extends State<ChatPage> {
114220 );
115221 }
116222
117- bool isLoading = false ;
118-
119- Widget _messageList (BuildContext ctx) {
120- return Expanded (
121- child: Consumer <MessageState >(builder: (_, state, __) {
122- var reversed = state.messages.reversed;
123- return ListView .separated (
124- separatorBuilder: (_, __) {
125- return Divider (color: Colors .grey, height: 1.0 );
126- },
127- itemCount: state.messages.length,
128- itemBuilder: (ctx, index) => ChatBubble (
129- message: reversed.elementAt (index),
130- ),
131- controller: scrollController,
132- padding: EdgeInsets .symmetric (horizontal: 10 ),
133- );
134- }),
135- );
136- }
137-
138223 Future _getMessages () async {
139224 var roomId = widget.roomId;
140225 if (roomId == null ) return ;
141226 var messageState = Provider .of <MessageState >(context, listen: false );
142227 await messageState.getAllMessage (roomId);
143- messageState.subscribeChatRoom (messageReceivedCallback: () {
144- _animateScrollToBottom ();
145- });
146- }
147-
148- void _animateScrollToBottom () {
149- Timer (const Duration (milliseconds: 400 ), () {
150- scrollController.animateTo (
151- scrollController.position.maxScrollExtent,
152- duration: const Duration (milliseconds: 1000 ),
153- curve: Curves .ease,
154- );
155- });
228+ messageState.subscribeChatRoom ();
156229 }
157230
158231 Future _getRoom () async {
@@ -163,8 +236,40 @@ class _ChatState extends State<ChatPage> {
163236 roomState.subscribe (room);
164237 }
165238
166- @override
167- void dispose () {
168- super .dispose ();
239+ Future <void > _loadMore () async {
240+ setState (() {
241+ isLoading = true ;
242+ });
243+ await _messageState.getPreviousMessage (widget.roomId);
244+ setState (() {
245+ isLoading = false ;
246+ });
247+ }
248+
249+ Widget _messageList (BuildContext ctx) {
250+ return Expanded (
251+ child: NotificationListener <ScrollNotification >(
252+ onNotification: (notification) {
253+ scroll$.add (notification);
254+ return true ;
255+ },
256+ child: Consumer <MessageState >(builder: (_, state, __) {
257+ var reversed = state.messages.reversed;
258+ return ListView .separated (
259+ separatorBuilder: (_, __) {
260+ return Divider (color: Colors .grey, height: 1.0 );
261+ },
262+ itemCount: state.messages.length,
263+ itemBuilder: (ctx, index) =>
264+ ChatBubble (
265+ message: reversed.elementAt (index),
266+ ),
267+ controller: scrollController,
268+ padding: EdgeInsets .symmetric (horizontal: 10 ),
269+ reverse: true ,
270+ );
271+ }),
272+ ),
273+ );
169274 }
170275}
0 commit comments